﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using WheelOfCivilization.Map.MapObject.Movable;
using WheelOfCivilization.Map.MapObject.Unmovable.Buildings;

namespace WheelOfCivilization.Map
{
    class World
    {
        public List<WorldCluster> clusterList = new List<WorldCluster>(); //seznam vytvorenych clusteru //list of generated clusters

        //<summary>
            //CZ: v konstruktoru World() vytvarim instanci noveho clusteru a pridani do svetove mapy
            //EN: in constructor of Worls() I'm creating first instance of map cluster && adding to world "map"
        //</summary>
        public World()
        {
            WorldCluster first = new WorldCluster();    
            clusterList.Add(first);                     
        }


        //<summary>
        //CZ: vytvarim instanci za saveFilu, zatim neresit
        //EN: Creating World from saveFile, not to solve now
        //</summary>
        public World(string fileLocation)               
        {
            StreamReader load = new StreamReader(fileLocation);
            while (!load.EndOfStream)
            {
                StringBuilder buildCluster = new StringBuilder();
                while (true)
                {
                    string tmp = load.ReadLine();
                    if (tmp == null)
                    {
                        break;
                    }
                    if (tmp == "</location>")
                    {
                        buildCluster.AppendLine(tmp);
                        break;
                    }
                    buildCluster.AppendLine(tmp);
                }
                try
                {
                    WorldCluster wc = new WorldCluster(buildCluster.ToString());
                    clusterList.Add(wc);
                }
                catch
                {
                    Console.WriteLine("Chyba pri tvoreni clusteru z XML!");
                }
                
            }
            foreach(WorldCluster wc in clusterList)
            {
                recreateNeigborhood(wc);
            }
            Logic.MovementLogic.clusterGates(clusterList);
        }

        //<summary>
        //CZ: jelikoz nechci ukladat zbytecne vsechno, pole neigbourhood dopocitavam po nacteni, zatim neresit
        //EN: As I dont wan't to save everything in the file, I calculate some elements of each cluster later, not to solve now
        //</summary>
        private void recreateNeigborhood(WorldCluster wc) 
        {
            byte[] posibble = new byte[3];
            for (int i = wc.X - 1; i < wc.X + 2; i++)
            {
                if (i-wc.X < 0)
                {
                    posibble = new byte[3] { 7, 6, 5 };
                }
                else
                {
                    if (i-wc.X > 0)
                    {
                        posibble = new byte[3] { 1, 2, 3 };
                    }
                    else
                        posibble = new byte[3] { 4, 255, 0 };
                }
                for (int j = wc.Y - 1; j < wc.Y + 2; j++)
                {
                    if (j != wc.Y || i != wc.X)
                    {
                        var tmp = clusterList.Where(w => w.X == i && w.Y == j).FirstOrDefault();
                        if (tmp != null)
                        {
                            wc.neigbourhood[posibble[wc.Y - j + 1]] = tmp;
                        }
                    }
                }
            }
        }

        //<summary>
        //CZ: ukladani, prozmenu neresit
        //EN: saving, not to solve now
        //</summary>
        public bool saveGame(string fileLocation)       
        {
            StreamWriter save = null;
            try
            {
                save = new StreamWriter(fileLocation);
            }
            catch (System.IO.DirectoryNotFoundException)
            {
                //FileStream fs = new FileStream(
            }

            StringBuilder saveString = new StringBuilder();

            foreach (WorldCluster currentCluster in clusterList)
            {
                saveString.AppendFormat("<location {0}, {1}>", currentCluster.X, currentCluster.Y);
                saveString.AppendLine();
                saveString.AppendFormat("   <unmovableContent>");
                saveString.AppendLine();
                for (int i = 0; i < 10; i++)
                {
                    for (int j = 0; j < 10; j++)
                    {
                        saveString.AppendFormat("      <{0}>", currentCluster.unmovableContentGS[i, j].ToString());
                    }
                    saveString.AppendLine();
                }
                saveString.AppendFormat("   </unmovableContent>");
                saveString.AppendLine();
                saveString.AppendFormat("   <movableContent>");
                saveString.AppendLine();
                foreach (MovingObject mo in currentCluster.unitsGS)
                {
                    saveString.AppendFormat("      <{0}, {1}, {2}>", mo.ToString(), mo.pos.X, mo.pos.Y);
                    saveString.AppendLine();
                }
                saveString.AppendFormat("   </movableContent>");
                saveString.AppendLine();

                saveString.AppendFormat("   <buildings>");
                saveString.AppendLine();
                foreach (BuildingObject b in currentCluster.buildingsGS)
                {
                    saveString.AppendFormat("      <{0}, {1}, {2}>", b.ToString(), b.position.X, b.position.Y);
                    saveString.AppendLine();
                }
                saveString.AppendFormat("   </buildings>");
                saveString.AppendLine();
                saveString.AppendFormat("</location>");
                saveString.AppendLine();


                //saveString.AppendFormat("   <borders>/n");
                //for (int borderID = 0; borderID < 4; borderID++)
                //{
                //    saveString.Append("      <");
                //    foreach (int[] borderLocation in currentCluster.bordersGS[borderID])
                //    {
                //        saveString.AppendFormat("{0},{1}", borderLocation[0], borderLocation[1]);
                //    }
                //    saveString.Append("      >");
                //}
            }
            if (save != null)
            {
                save.Write(saveString);
                return true;
            }
            return false;
        }

        //<summary>
        //CZ: generovani noveho clusteru -> musi vedet kterej je parent a na ktery strane je, aby se mohly dopocitat souradnice/pripadne zjistit, 
            //jestli uz se na ty souradnice nedostal nekdo jinej
        //EN: generating new cluster, needs to know which is parent and which direction is the map expanding to get coordinates, 
            //later on to find out, if some other player didn't expanded the part
        //</summary>
        public WorldCluster generateCluster(WorldCluster parent, byte orientation) 
        {
            WorldCluster wc = null;
            #region long bad switch
            switch (orientation) //this switch is stupid, to rewrite : orientation can be expressed as it is lower in the code in the nested if/else construction
            {
                case 0:
                    {
                        wc = new WorldCluster(parent.X, parent.Y - 1);
                        break;
                    }
                case 1:
                    {
                        wc = new WorldCluster(parent.X + 1, parent.Y - 1);
                        break;
                    }
                case 2:
                    {
                        wc = new WorldCluster(parent.X + 1, parent.Y);
                        break;
                    }
                case 3:
                    {
                        wc = new WorldCluster(parent.X + 1, parent.Y + 1);
                        break;
                    }
                case 4:
                    {
                        wc = new WorldCluster(parent.X, parent.Y + 1);
                        break;
                    }
                case 5:
                    {
                        wc = new WorldCluster(parent.X - 1, parent.Y + 1);
                        break;
                    }
                case 6:
                    {
                        wc = new WorldCluster(parent.X - 1, parent.Y);
                        break;
                    }
                case 7:
                    {
                        wc = new WorldCluster(parent.X - 1, parent.Y - 1);
                        break;
                    }
            }
            #endregion
            //CZ: kontrola, jestli generovani probehlo spravne (protoze jestli nekde hloubs vznikne Exception nevhodne osetrena, mohlo by se vratit null a to by byl pruser napriklad pro navigaci)            
            //EN: kontrol if everything in "lower" levels went OK
            if (wc != null) 
            {
                ////CZ: nastaveni sousedstvi
                ////EN: setting up neigbourhood - more-> there should be method to set up whole neighbourhood, not only parent 
                //        //(e.g. cluster generated in location (1,1) from parent (1,0) won't see (0,1) which could end up with navigation error later on)
                //parent.neigbourhood[orientation] = wc; 
                //if (orientation > 3)
                //    wc.neigbourhood[orientation-4] = parent;
                //else
                //    wc.neigbourhood[orientation+4] = parent;

                setNeigbourhood(wc);

                //CZ: if orientation % 2 == 0 -> clustery nesousedi pouze rohem, ale celou stranou
                //EN: if orientation % 2 == 0 -> clusters are negbours not only with corner, but with whole side
                if(orientation % 2 == 0)
                    //CZ: najdu body, kudy se da prechazet z clusteru do clusteru
                    //EN: finding points/coordinates, which can be used to move from cluster to cluster
                    calculateBorders(parent, wc, orientation);
                //CZ: na zaver pridam novej cluster do seznamu (svetovy mapy)
                //EN: adding cluster to world map
                //clusterList.Add(wc);                    
                return wc;
            }
            else
            {
                throw new Exception("Error while populating cluster"); //kdy se mi vrati null, tak sem v <|>
            }
        }

        private void setNeigbourhood(WorldCluster wc)
        {
            for (int x = wc.X - 1; x < wc.X + 2; x++)
            {
                for (int y = wc.Y - 1; y < wc.Y + 2; y++)
                {
                    if(x != wc.X || y != wc.Y)
                    {
                        System.Threading.Monitor.Enter(Program.world.clusterList);
                        var tmp = Program.world.clusterList.Where(w => w.X == x && w.Y == y).FirstOrDefault();
                        System.Threading.Monitor.Exit(Program.world.clusterList);
                        if (tmp != null)
                        {
                            switch (wc.X - x)
                            {
                                case 1:
                                    {
                                        switch (wc.Y - y)
                                        {
                                            case 1:
                                                {
                                                    wc.neigbourhood[7] = tmp;
                                                    tmp.neigbourhood[3] = wc;
                                                    break;
                                                }
                                            case 0:
                                                {
                                                    wc.neigbourhood[6] = tmp;
                                                    tmp.neigbourhood[2] = wc;
                                                    break;
                                                }
                                            case -1:
                                                {
                                                    wc.neigbourhood[5] = tmp;
                                                    tmp.neigbourhood[1] = wc;
                                                    break;
                                                }

                                        }
                                        break;
                                    }
                                case 0:
                                    {
                                        switch (wc.Y - y)
                                        {
                                            case 1:
                                                {
                                                    wc.neigbourhood[0] = tmp;
                                                    tmp.neigbourhood[4] = wc;
                                                    break;
                                                }
                                            case -1:
                                                {
                                                    wc.neigbourhood[4] = tmp;
                                                    tmp.neigbourhood[0] = wc;
                                                    break;
                                                }
                                        }
                                        break;
                                    }
                                case -1:
                                    {
                                        switch (wc.Y - y)
                                        {
                                            case 1:
                                                {
                                                    wc.neigbourhood[1] = tmp;
                                                    tmp.neigbourhood[5] = wc;
                                                    break;
                                                }
                                            case 0:
                                                {
                                                    wc.neigbourhood[2] = tmp;
                                                    tmp.neigbourhood[6] = wc;
                                                    break;
                                                }
                                            case -1:
                                                {
                                                    wc.neigbourhood[3] = tmp;
                                                    tmp.neigbourhood[7] = wc;
                                                    break;
                                                }

                                        }
                                        break;
                                    }
                            }
                            //if (x < wc.X)
                            //{
                            //    if (y == wc.Y - 1)
                            //    {
                            //        wc.neigbourhood[7] = tmp;
                            //        tmp.neigbourhood[3] = wc;
                            //    }
                            //    else
                            //    {
                            //        if(
                            //    }
                            //}
                        }
                    }
                }
            }
        }

        //<summary>
        //CZ: pocitam vstupni body mezi clustery - musim znat orientaci
        //EN: finding points/coordinates, which can be used to move from cluster to cluster - I need to know orientation of the clusters
        //</summary>
        internal void calculateBorders(WorldCluster a, WorldCluster b, byte orientation) 
        {
            //CZ: tahle metoda by si zaslouzila nejakou upravu, je hrozne ukecana. 
                //Celkove ty orientace mam udelany debilne, pac mi az asi po tejdnu doslo, ze jdou jednoduse pocitat jako je to o par radku vejs v generateCluster();
            //EN: I pretty much don't like this method and mine "expansive" switch/case constructions in global, this should be done in some more elegant way
            
            switch (orientation)
            {
                case 0: //CZ: horni hranice a, dolni hranice b //EN: upper a, lower b
                    for (int i = 0; i < 10; i++) 
                    {
                        //EN: for each point of border, I'm asking whether the point is occupied by some unmovable object (tree, stone, house, wall, your mom)
                        if (!a.unmovableContentGS[i, 0].occupied && !b.unmovableContentGS[i, 9].occupied)
                        {
                            a.bordersGS[0].Add(new int[] { i, 0 });
                            b.bordersGS[2].Add(new int[] { i, 9 });
                        }
                    }
                    break;
                case 2: //CZ: prava a, leva b //EN: right a, left b
                    for (int i = 0; i < 10; i++)
                    {
                        if (!a.unmovableContentGS[9, i].occupied && !b.unmovableContentGS[0, i].occupied)
                        {
                            a.bordersGS[1].Add(new int[] { 9, i });
                            b.bordersGS[3].Add(new int[] { 0, i });
                        }
                    }
                    break;
                case 4://CZ: dolni a, horni b //EN: upper b, lower a
                    for (int i = 0; i < 10; i++)
                    {
                        if (!a.unmovableContentGS[i, 9].occupied && !b.unmovableContentGS[i, 0].occupied)
                        {
                            a.bordersGS[2].Add(new int[] { i, 9 });
                            b.bordersGS[0].Add(new int[] { i, 0 });
                        }
                    }
                    break;
                case 6://leva a, prava b //EN: right b, left a
                    for (int i = 0; i < 10; i++)
                    {
                        if (!a.unmovableContentGS[0, i].occupied && !b.unmovableContentGS[9, i].occupied)
                        {
                            a.bordersGS[3].Add(new int[] { 0, i });
                            b.bordersGS[1].Add(new int[] { 9, i });
                        }
                    }
                    break;
            }
           
        }

        //<summary>
        //old method used while I tried basics in console, calculates size of generated map
        //</summary>
        internal int[] calculateGrid()
        {
            int xmin = int.MaxValue, xmax = int.MinValue;
            int ymin = int.MaxValue, ymax = int.MinValue;
            foreach (WorldCluster wc in clusterList)
            {
                if (wc.X < xmin)
                {
                    xmin = wc.X;
                }
                if (wc.Y < ymin)
                {
                    ymin = wc.Y;
                }
                if (wc.X > xmax)
                {
                    xmax = wc.X;
                }
                if (wc.Y > ymax)
                {
                    ymax = wc.Y;
                }
            }
            int xsize, ysize;
            xsize = xmax - xmin + 1;
            ysize = ymax - ymin + 1;
            return new int[2]{ xsize, ysize };
        }
    }
}
